package core.tree.nj;

import core.sb.SB;
import core.tree.Tree;
import core.tree.Vertex;
import gui.processor.Processor;
import gui.utils.Para;
import gui.utils.ProgBar;
import java.util.ArrayList;

/*Author: John Archer, Contact: john.archer.jpa@gmail.com*/
public class NJ {
    private ArrayList <ArrayList <Double>> L_SCORES;
    private ArrayList <Vertex> L;
    private Double doubleNull = new Double(0.000123);
    private Processor p;

    public NJ(Processor gp){this.p = gp;}

    public Tree build(SB al, boolean treeDar) {
        L = new ArrayList();
        if(treeDar){for (int x = 0; x < al.getSize(); x++) {L.add(new Vertex(al.get(x).getI()));}}
        else{for (int x = 0; x < al.getSize(); x++) {L.add(new Vertex(al.get(x).getTitle(), al.get(x).getI()));}}

        L_SCORES = new ArrayList();
        for (int x = 0; x < L.size(); x++) {
            L_SCORES.add(new ArrayList());
            for (int y = 0; y < L.size(); y++){ L_SCORES.get(L_SCORES.size()-1).add(doubleNull); }
        }
        Vertex vert1;Vertex vert2;
        if(p!=null){
            p.msg("Calculating Pairwise Distances");
            p.dPrg(ProgBar.START, L.size());
        }
        for (int x = 0; x < L.size(); x++) {
            vert1 = L.get(x);
            for (int y = 0; y < L.size(); y++) {
                vert2 = L.get(y);
                L_SCORES.get(x).set(y, getDivergence(
                        al.getSeqByI(vert1.getI()).getDNA(),
                        al.getSeqByI(vert2.getI()).getDNA()
                ));
            }
            if(p!=null){p.dPrg(ProgBar.UPDATE, x);}
        }
        if(p!=null){p.dPrg(ProgBar.END, 0);}
        int i, j;
        double minDij, Dij, ri, rj, dij, dik;
        Vertex vi, vj, vk;
        if(p!=null){
            p.msg("Neighbour Joining");
            p.dPrg(ProgBar.START, L.size());
        }
        int count = 0;
        while (L.size() > 2) {
            i = 0; j = 1;
            minDij = Double.MAX_VALUE;
            for (int I = 0; I < L.size(); I++) {
                for (int J = I + 1; J < L.size()-1; J++) {
                    Dij = d(I, J) - (r(I) + r(J));
                    if (Dij < minDij && I != J){i = I;j = J;minDij = Dij;}
                }
            }
            vi = L.get(i); vj = L.get(j);
            vk = new Vertex(-1);
            dij = d(i, j);
            ri = r(i); rj = r(j);
            dik = (dij + ri - rj) / 2;
            dik = negCheck(dik);
            vi.setDistToParent(dik);
            vj.setDistToParent(negCheck(dij - dik));
            vk.addChild(vi); vk.addChild(vj);
            L.add(vk); addKtoL(i, j, dij);
            L.remove(vi); L.remove(vj); rmIJfromL(i, j);
            if(p!=null){p.dPrg(ProgBar.UPDATE, count++);}
        }
        if(p!=null){p.dPrg(ProgBar.END, 0);}
        vi = L.get(0); vj = L.get(1);
        vi.addChild(vj);
        vj.setDistToParent(d(0, 1));
        Tree t;
        if(!treeDar){t = new Tree(vi, Tree.INITIAL_TREE_MAG);}
        else{t = new Tree(vi, Para.TREEDAR_MAG);}
        return t;
    }

    private double negCheck(double d){return (d > 0) ? d : 0;}

    private void rmIJfromL(int i, int j) {
        if(i>j){
            for(int x=0; x<L_SCORES.size(); x++){L_SCORES.get(x).remove(i);}L_SCORES.remove(i);
            for(int x=0; x<L_SCORES.size(); x++){L_SCORES.get(x).remove(j);}L_SCORES.remove(j);
        }
        else{
            for(int x=0; x<L_SCORES.size(); x++){L_SCORES.get(x).remove(j);}L_SCORES.remove(j);
            for(int x=0; x<L_SCORES.size(); x++){L_SCORES.get(x).remove(i);} L_SCORES.remove(i);
        }
    }

    private void addKtoL(int i, int j, double dij){
        ArrayList newRow = new ArrayList();
        double dkm;
        for (int m = 0; m < L_SCORES.size(); m++){
            dkm = (d(i, m) + d(j, m) - dij) / 2;
            L_SCORES.get(m).add(dkm);
            newRow.add(dkm);
        }
        newRow.add(doubleNull);
        L_SCORES.add(newRow);
    }

    private double r(int i) {
        double sum = 0.00;
        for (int k = 0; k < i; k++){sum += d(i, k);}
        for (int k = i + 1; k < L_SCORES.size(); k++) {sum += d(i, k);}
        return sum / (L_SCORES.size() - 2);
    }

    private double d(int i, int j){return L_SCORES.get(i).get(j);}
    private double getDivergence(String s1, String s2) {
        double sum = 0.00;
        int gaps = 0;
        for(int x = 0; x < s1.length(); x++){
            if(s1.charAt(x) != '-' && s2.charAt(x)!= '-' ){if(s1.charAt(x) != s2.charAt(x)){sum++;}}
            else{gaps++;}
        }
        return sum/(double)(s1.length() - gaps);
    }
}